#include <iostream>
#include <sstream>
#include <string>
#include <list>
#include <cassert>
#include "Position.h"
#include "Types.h"

using namespace std;

Position::Position()
{
	//SHOULD ALWAYS BE SET UP WITH ANOTHER FUNCTION

	//pieceBB = new Bitboard[14];
	//board = new Piece[64];
	//for(int i=0;i<64;i++) board[i] = NO_PIECE;
	//lastEnPassant = SQ_NONE;
}

Position::Position(const Position& p)
{
	//board = new Piece[64];
	this->whiteCanCastleKingSide = p.whiteCanCastleKingSide;
	this->whiteCanCastleQueenSide = p.whiteCanCastleQueenSide;
	this->blackCanCastleQueenSide = p.blackCanCastleQueenSide;
	this->blackCanCastleKingSide = p.blackCanCastleKingSide;
	//for(int i=0;i<64;i++) board[i] = p.board[i];
	memcpy( (void*)board, p.board, sizeof(board) );
	turn = p.turn;
	//pieceBB = new Bitboard[14];
	lastEnPassant = p.lastEnPassant;
}

Position::Position(const std::string& fen, bool isChess960, int count)
{
	// TODO: see if this init is needed
	//for(int i=0;i<64;i++) board[i] = NO_PIECE;
	from_fen(fen, isChess960);
	lastEnPassant = SQ_NONE;
}

Position::~Position(void)
{
	//delete pieceBB;
}

const string PieceToChar("PNBRQKpnbrqk");

void Position::from_fen(const std::string& fenStr , bool isChess960) {

  char col, row, token;
  size_t p;
  Square sq = SQ_A8;

  istringstream fen(fenStr,istringstream::in);

  Clear();
  lastEnPassant = SQ_NONE;
  fen >> std::noskipws;

  // 1. Piece placement
  while ((fen >> token) && !isspace(token))
  {
      if (isdigit(token))
          sq += Square(token - '0'); // Advance the given number of files

      else if (token == '/')
          sq = make_square(FILE_A, rank_of(sq) - Rank(2));

      else if ((p = PieceToChar.find(token)) != string::npos)
      {
          board[sq] = Piece(p);
          sq++;
      }
  }

  // 2. Active color
  fen >> token;
  //sideTomove = (token == 'w' ? WHITE : BLACK);
  turn = (token == 'w' ? WHITE : BLACK);
  fen >> token;

  // 3. Castling availability. Compatible with 3 standards: Normal FEN standard,
  // Shredder-FEN that uses the letters of the columns on which the rooks began
  // the game instead of KQkq and also X-FEN standard that, in case of Chess960,
  // if an inner rook is associated with the castling right, the castling tag is
  // replaced by the file letter of the involved rook, as for the Shredder-FEN.
	whiteCanCastleKingSide = false;
	whiteCanCastleQueenSide = false;
	blackCanCastleKingSide = false;
	blackCanCastleQueenSide = false;
  while ((fen >> token) && !isspace(token))
  {
      Square rsq;
      Color c = islower(token) ? BLACK : WHITE;

      token = char(toupper(token));

	  if (token == 'K') {
          //for (rsq = relative_square(c, SQ_H1); type_of(GetPiece(rsq)) != ROOK; rsq--) {}
		  if(c==WHITE)
			  whiteCanCastleKingSide = true;
		  else
			  blackCanCastleKingSide = true;
	  }
	  else if (token == 'Q') {
          //for (rsq = relative_square(c, SQ_A1); type_of(GetPiece(rsq)) != ROOK; rsq++) {}
		  if(c==WHITE)
			  whiteCanCastleQueenSide = true;
		  else
			  blackCanCastleQueenSide = true;
	  }
      else if (token >= 'A' && token <= 'H')
          rsq = make_square(File(token - 'A'), relative_rank(c, RANK_1));

      else
          continue;

      //set_castle_right(c, rsq);
  }


  // 4. En passant square. Ignore if no pawn capture is possible
	char c;
	fen >> c;
		if( c != '-') {
			fen >> c;
			fen >> col >> row;
			lastEnPassant = make_square(File(col - 'a'), Rank(row - '1'));
		}
		/*
  if (   ((fen >> col) && (col >= 'a' && col <= 'h'))
      && ((fen >> row) && (row == '3' || row == '6')))
  {
      st->epSquare = make_square(File(col - 'a'), Rank(row - '1'));
	  lastEnPassant = make_square(File(col - 'a'), Rank(row - '1'));

      //if (!(attackers_to(st->epSquare) & pieces(PAWN, sideTomove)))
	  if (!(attackers_to(st->epSquare) & pieces(PAWN, turn?0:1))) // change turn part
          st->epSquare = SQ_NONE;
  }*/

  // 5-6. Halfmove clock and fullmove number
  //fen >> std::skipws >> st.rule50 >> startPosPly;
//TODO: change this init
  fen >> std::skipws >> startPosPly >> startPosPly;

  // Convert from fullmove starting from 1 to ply starting from 0,
  // handle also common incorrect FEN with fullmove = 0.
  //startPosPly = std::max(2 * (startPosPly - 1), 0) + int(sideTomove == BLACK);
	startPosPly = std::max(2 * (startPosPly - 1), 0) + (turn?0:1);


//  st->key = compute_key();
//  st->pawnKey = compute_pawn_key();
//  st->materialKey = compute_material_key();
//  st->value = compute_value();
//  st->npMaterial[WHITE] = compute_non_pawn_material(WHITE);
//  st->npMaterial[BLACK] = compute_non_pawn_material(BLACK);
//  st->checkersBB = attackers_to(king_square(sideTomove)) & pieces(~sideTomove);
  //chess960 = isChess960;

  //assert(pos_is_ok());
}

void Position::SetFromFEN(const std::string& fen)
{
    this->Clear();
    int square = 56;

	/*
    string[] parameters = fenString.Split(' ');
    String board = parameters[0];
    char ply = parameters[1][0];
    String castling = parameters[2];
    String enPassant = parameters[3];
    String halfmoveClock = parameters[4];
    String fullmoveNumber = parameters[5];

    foreach (char carBoard in board)
    {
        switch (carBoard)
        {
            case 'K':
                this->SetPiece(Piece.WK, square);
                square++;
                break;
            case 'Q':
                this->SetPiece(Piece.WQ, square);
                square++;
                break;
            case 'R':
                this->SetPiece(Piece.WR, square);
                square++;
                break;
            case 'N':
                this->SetPiece(Piece.WN, square);
                square++;
                break;
            case 'B':
                this->SetPiece(Piece.WB, square);
                square++;
                break;
            case 'P':
                this->SetPiece(Piece.WP, square);
                square++;
                break;
            case 'k':
                this->SetPiece(Piece.BK, square);
                square++;
                break;
            case 'q':
                this->SetPiece(Piece.BQ, square);
                square++;
                break;
            case 'r':
                this->SetPiece(Piece.BR, square);
                square++;
                break;
            case 'n':
                this->SetPiece(Piece.BN, square);
                square++;
                break;
            case 'b':
                this->SetPiece(Piece.BB, square);
                square++;
                break;
            case 'p':
                this->SetPiece(Piece.BP, square);
                square++;
                break;
            case '/':
                square-=16;
                break;
            case '1':
                square += 1;
                break;
            case '2':
                square += 2;
                break;
            case '3':
                square += 3;
                break;
            case '4':
                square += 4;
                break;
            case '5':
                square += 5;
                break;
            case '6':
                square += 6;
                break;
            case '7':
                square += 7;
                break;
            case '8':
                square += 8;
                break;
            default:
                // TODO Throw incorrect data exception
                break;
        }
    }

    switch (ply)
    {
        case 'w':
            this->SetWhiteTomove();
            break;
        case 'b':
            this->SetBlackTomove();
            break;
        default:
            // TODO Throw incorrect data exception
            break;
    }


    this->whiteCanCastleKingSide = false;
    this->whiteCanCastleQueenSide = false;
    this->blackCanCastleKingSide = false;
    this->blackCanCastleQueenSide = false;
    foreach (char carCastle in castling)
    {
        switch (carCastle)
        {
            case 'K':
                this->whiteCanCastleKingSide = true;
                break;
            case 'Q':
                this->whiteCanCastleQueenSide = true;
                break;
            case 'k':
                this->blackCanCastleKingSide = true;
                break;
            case 'q':
                this->blackCanCastleQueenSide = true;
                break;
            default:
                // TODO Throw incorrect data exception
                break;
        }

    }

    if (enPassant[0] != '-')
    {
        this->lastEnPassant = (enPassant[0]-'a') + 8*(enPassant[1]-'1');
    }

    this->lastWhitePawnmove = int.Parse(halfmoveClock);
    this->lastBlackPawnmove = int.Parse(fullmoveNumber);

	*/
}

/*
inline Piece Position::piece_moved(Move m) const {
	return board[from_sq(m)];
}*/

inline bool Position::square_is_empty(Square s) const {
  return board[s] == NO_PIECE;
}

void Position::print(/*move move*/) const {

  const char* dottedLine = "\n+---+---+---+---+---+---+---+---+\n";
/*
  if (move)
  {
      Position p(*this, thread());
      cout << "\nmove is: " << (sideTomove == BLACK ? ".." : "") << move_to_san(p, move);
  }
*/
  for (Rank rank = RANK_8; rank >= RANK_1; rank--)
  {
      cout << dottedLine << '|';
      for (File file = FILE_A; file <= FILE_H; file++)
      {
          Square sq = make_square(file, rank);
          Piece piece = board[sq];
          char c = (color_of(piece) == BLACK ? '=' : ' ');

		  /*
          if (piece == NO_PIECE && !opposite_colors(sq, SQ_A1))
              piece++; // Index the dot
*/
          cout << c << PieceToChar[piece] << c << '|';
      }
  }
//  cout << dottedLine << "Fen is: " << to_fen() << "\nKey is: " << st->key << endl;
  
}

void Position::flip_me(){
  // TODO implement this method
}

/***
* Perform the move and change turn
* 
* @param m
*/
void Position::Play(Move m) {
    int piece = GetPiece(from_sq(m));
    //int dest = GetPiece(m.to);
    // En-passant extra-move (remove opponant's pawn)
	if (IsEmpty(to_sq(m)) && (file_of(from_sq(m)) != file_of(to_sq(m))))
    {
        if (piece == W_PAWN)
        {
			SetPiece(NO_PIECE, to_sq(m) - 8);
        }
        if (piece == B_PAWN)
        {
            SetPiece(NO_PIECE, to_sq(m) + 8);
        }
    }

    // Castleling extra-move (Rook)
    if (piece == W_KING && from_sq(m) == SQ_E1 && to_sq(m) == SQ_G1)
    {
        movePiece(SQ_H1, SQ_F1);
    }
    if (piece == W_KING && from_sq(m) == SQ_E1 && to_sq(m) == SQ_C1)
    {
        movePiece(SQ_A1, SQ_D1);
    }
    if (piece == B_KING && from_sq(m) == SQ_E8 && to_sq(m) == SQ_G8)
    {
        movePiece(SQ_H8, SQ_F8);
    }
    if (piece == B_KING && from_sq(m) == SQ_E8 && to_sq(m) == SQ_C8)
    {
        movePiece(SQ_A8, SQ_D8);
    }

    // Castleling - canceller
    if(piece == W_KING)
    {
        whiteCanCastleKingSide = false;
        whiteCanCastleQueenSide = false;
    } else
    if(piece == B_KING)
    {
        blackCanCastleKingSide = false;
        blackCanCastleQueenSide = false;
    } else
    if(piece == W_ROOK && from_sq(m) == SQ_A1)
    {
        whiteCanCastleQueenSide = false;
    } else
    if (piece == W_ROOK && from_sq(m) == SQ_H1)
    {
        whiteCanCastleKingSide = false;
    } else
    if (piece == B_ROOK && from_sq(m) == SQ_A8)
    {
        blackCanCastleQueenSide = false;
    } else
    if (piece == B_ROOK && from_sq(m) == SQ_H8)
    {
        blackCanCastleKingSide = false;
    }

    // keeping en-passant last move
	if((piece == W_PAWN && (to_sq(m)-from_sq(m))==16 )
        || (piece == B_PAWN && (to_sq(m) - from_sq(m)) == 16))
    {
		lastEnPassant = to_sq(m);
    } else
    {
		lastEnPassant = SQ_NONE;
    }
     
    // standard move
	movePiece(from_sq(m),to_sq(m));

    // promotion
    if(promotion_type(m) != NO_PIECE_TYPE)
    {
        switch (promotion_type(m))
        {
            case QUEEN:
				if(turn) SetPiece(W_QUEEN,to_sq(m));
				else SetPiece(B_QUEEN, to_sq(m));
                break;
            case ROOK:
				if (turn) SetPiece(W_ROOK, to_sq(m));
				else SetPiece(B_ROOK, to_sq(m));
                break;
			case BISHOP:
				if (turn) SetPiece(W_BISHOP, to_sq(m));
				else SetPiece(B_BISHOP, to_sq(m));
                break;
			case KNIGHT:
				if (turn) SetPiece(W_KNIGHT, to_sq(m));
                else SetPiece(B_KNIGHT, to_sq(m));
                break;
        }

    }

    turn = !turn;
}

/***
* Unplay the move and change turn
* 
* @param m
*/
// TODO: see if move history must be an attribute of Position or not (then no need for the move param)
void Position::Unplay(Move move){
	// TODO implement this method (from C# version)

}

bool Position::IsPlayer(int square) {
	//return ((1 << square) & pieceBB[turn?0:1]); 
	Piece p = board[square];
	if(turn == WHITE) return (p == W_PAWN) || (p == W_KNIGHT) || (p == W_BISHOP) || (p == W_ROOK) || (p == W_QUEEN) || (p == W_KING);
	return (p == B_PAWN) || (p == B_KNIGHT) || (p == B_BISHOP) || (p == B_ROOK) || (p == B_QUEEN) || (p == B_KING);
}

bool Position::IsEmptyOrOpponent(int square) {
	//return ((1 << square) & pieceBB[turn?1:0]); 
	Piece p = board[square];
	if(IsEmpty(square)) return true;
	if(turn == BLACK) return (p == W_PAWN) || (p == W_KNIGHT) || (p == W_BISHOP) || (p == W_ROOK) || (p == W_QUEEN) || (p == W_KING);
	return (p == B_PAWN) || (p == B_KNIGHT) || (p == B_BISHOP) || (p == B_ROOK) || (p == B_QUEEN) || (p == B_KING);
}

bool Position::IsEmptyOrColor(int square, bool color) { 
	Piece p = board[square];
	if(IsEmpty(square)) return true;
	if(color == BLACK) return (p == B_PAWN) || (p == B_KNIGHT) || (p == B_BISHOP) || (p == B_ROOK) || (p == B_QUEEN) || (p == B_KING);
	return (p == W_PAWN) || (p == W_KNIGHT) || (p == W_BISHOP) || (p == W_ROOK) || (p == W_QUEEN) || (p == W_KING);

}

inline Piece Position::GetPiece(int square){
	assert(square>=0 && square<=63);
	return board[square];
}

bool Position::IsPieceWhite(int square)
{
    //return pieceBB[0] & (1 << square);
	Piece p = board[square];
	return (p == W_PAWN) || (p == W_KNIGHT) || (p == W_BISHOP) || (p == W_ROOK) || (p == W_QUEEN) || (p == W_KING);
}

bool Position::IsPieceBlack(int square)
{
    //return pieceBB[1] & (1 << square);
	Piece p = board[square];
	return (p == B_PAWN) || (p == B_KNIGHT) || (p == B_BISHOP) || (p == B_ROOK) || (p == B_QUEEN) || (p == B_KING);
}

bool Position::IsEmpty(int square){
	//return emptyBB & (1 << square);
	return board[square] == NO_PIECE;
}
/*
bool Position::IsEmpty(Square square){
	//return emptyBB & (1 << square);
	return board[square] == NO_PIECE;
}*/

void Position::SetPiece(Piece piece, int square){
	board[square] = piece;
}

void Position::movePiece(int sq1, int sq2){
	board[sq2] = board[sq1];
	board[sq1] = NO_PIECE;
}

Position* Position::operator =(const Position &p){
	this->whiteCanCastleKingSide = p.whiteCanCastleKingSide;
	this->whiteCanCastleQueenSide = p.whiteCanCastleQueenSide;
	this->blackCanCastleQueenSide = p.blackCanCastleQueenSide;
	this->blackCanCastleKingSide = p.blackCanCastleKingSide;	
	memcpy( (void*)board, p.board, sizeof(board) );
	turn = p.turn;
	lastEnPassant = p.lastEnPassant;
	return this;
}

void Position::Clear(){

	for(int i=0;i<64;i++) board[i] = NO_PIECE;
}
/*
bool Position::is_chess960() {
	return chess960;
}*/